Disruption budgets з’явились в версії 0.36, і виглядає як дуже цікавий інструмент для того, аби обмежити Karpenter в перестворенні WorkerNodes.
Наприклад в моєму випадку ми не хочемо, аби EC2 вбивались в робочі часи по США, бо там у нас клієнти, а тому зараз маємо consolidationPolicy=whenEmpty, аби запобігти “зайвому” видаленню серверів та Pods на них.
Натомість з Disruption budgets ми можемо налаштувати політики таким чином, що в один період часу будуть дозволені операції з WhenEmpty, а в інший – WhenEmptyOrUnderutilized.
Див. також Kubernetes: забезпечення High Availability для Pods – бо при використанні Karpenter навіть при налаштованих Disruption budgets необхідно мати відповідно налаштовані поди з Topology Spread та PodDisruptionBudget.
Зміст
Типи Karpenter Disruption
Документація – Automated Graceful Methods.
Спочатку глянемо, в яких випадках Disruption взагалі відбувається:
- Drift: виникає, коли є різниця між створеними конфігураціями NodePools або EC2NodeClass та існуючими WorkerNodes – тоді Karpenter почне перестворювати EC2 аби привести їх у відповідність до заданих параметрів
- Interruption: якщо Karpenter отримує AWS Event, що інстанс буде виключено, наприклад – якщо це Spot
- Consolidation: якщо маємо налаштування Consolidation на
WhenEmptyOrUnderutilizedабоWhenEmpty, і Karpenter переносить наші Pods на інші WorkerNodes- у нас Karpenter 1.0, тому полісі
WhenEmptyOrUnderutilized, для 0.37 цеWhenUnderutilized
- у нас Karpenter 1.0, тому полісі
Karpenter Disruption Budgets
За допомогою Disruption budgets ми можемо дуже гнучко налаштувати в який час і які операції Karpenter може проводити, і задати ліміт на те, скільки WorkerNodes одночасно будуть видалятись.
Документація – NodePool Disruption Budgets.
Формат конфігурації доволі простий:
budgets: - nodes: "20%" reasons: - "Empty" schedule: "@daily" duration: 10m
Тут ми задаємо:
- дозволити видалення WorkerNodes для 20% від загальної кількості
- для операції, коли Disruption викликаний умовою
WhenEmpty - виконуємо це кожен день
- на протязі 10 хвилин
Параметри тут можуть мати значення:
nodes: в процентах або просто кількості нодreasons:Drifted,UnderutilizedабоEmptyschedule: розклад, за яким правило застосовується, в UTC (інші таймзони поки не підтримуються), див. Kubernetes Schedule syntaxduration: і скільки часу правило діє, наприклад –1h15m
При цьому не обов’язково задавати всі параметри.
Наприклад, ми можемо описати два таких бюджети:
- nodes: "25%" - nodes: "10"
Тоді у нас постійно будуть працювати обидва правила, і перше обмежує кількість нод в 25% від загальної кількості, а друге – не більше як 10 інстансів – якщо у нас більш ніж 40 серверів.
Також, Budgets можна комбінувати, і якщо їх задано кілька – то ліміти будуть братись по найбільш суворому.
В першому прикладі ми застосовуємо правило на 20% нод і умові WhenEmpty, а решту часу будуть працювати дефолтні правила disruption – тобто, 10% від загальної кількості серверів із заданою consolidationPolicy.
Тому можемо записати правило так:
budgets: - nodes: "20%" reasons: - "Empty" schedule: "@daily" duration: 10m - nodes: 0
Тут останнє правило працює постійно, і буде таким собі запобіжником: ми забороняємо все, але дозоляємо виконувати disruption за політикою WhenEmpty на протязі 10 хвилин раз на добу починаючи з 00:00 UTC.
Приклад Disruption Budgets
Повертаючись до моєї задачі:
- маємо Backend API в Kubernetes на окремому NodePool, а наші клієнти в основному з США, тому ми хочемо мінімізувати down-скейлінг WorkerNodes в робочий час по США
- для цього ми хочемо заблокувати всі операції по
WhenUnderutilizedв період робочого часу по Central Time USA- в
scheduleKarpenter використовує зону UTC, тому початок робочого дня по Central Time USA 9:00 – це 15:00 UTC
- в
- операції з
WhenEmptyдозволимо в будь-який час, але тільки по 1 WorkerNode одночасно Drift– аналогічно, бо коли я деплою зміни – то хочу побачити результат відразу
Фактично, нам потрібно задати два бюджети:
- по
Underutilized– забороняємо все з понеділка по п’ятницю на протязі 9 годин починаючи з 15:00 по UTC - по
EmptyтаDrifted– дозволяємо в будь-який час, але тільки по 1 ноді, а не дефолтні 10%
Тоді наш NodePool буде виглядати так:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: backend1a
spec:
template:
metadata:
labels:
created-by: karpenter
component: devops
spec:
taints:
- key: BackendOnly
operator: Exists
effect: NoSchedule
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: defaultv1a
requirements:
- key: karpenter.k8s.aws/instance-family
operator: In
values: ["c5"]
- key: karpenter.k8s.aws/instance-size
operator: In
values: ["large", "xlarge"]
- key: topology.kubernetes.io/zone
operator: In
values: ["us-east-1a"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
# total cluster limits
limits:
cpu: 1000
memory: 1000Gi
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 600s
budgets:
- nodes: "0" # block all
reasons:
- "Underutilized" # if reason == underutilized
schedule: "0 15 * * mon-fri" # starting at 15:00 UTC during weekdays
duration: 9h # during 9 hours
- nodes: "1" # allow by 1 WorkerNode at a time
reasons:
- "Empty"
- "Drifted"
Деплоїмо, перевіряємо NodePool:
$ kk describe nodepool backend1a
Name: backend1a
...
API Version: karpenter.sh/v1
Kind: NodePool
...
Spec:
Disruption:
Budgets:
Duration: 9h
Nodes: 0
Reasons:
Underutilized
Schedule: 0 15 * * mon-fri
Nodes: 1
Reasons:
Empty
Drifted
Consolidate After: 600s
Consolidation Policy: WhenEmptyOrUnderutilized
...
І в логах бачимо, що спрацював Disruption по WhenUnderutilized:
karpenter-55b845dd4c-tlrdr:controller {"level":"INFO","time":"2024-09-16T10:48:26.777Z","logger":"controller","message":"disrupting nodeclaim(s) via delete, terminating 1 nodes (2 pods) ip-10-0-42-250.ec2.internal/t3.small/spot","commit":"62a726c","controller":"disruption","namespace":"","name":"","reconcileID":"db2233c3-c64b-41f2-a656-d6a5addeda8a","command-id":"1cd3a8d8-57e9-4107-a701-bd167ed23686","reason":"underutilized"}
karpenter-55b845dd4c-tlrdr:controller {"level":"INFO","time":"2024-09-16T10:48:27.016Z","logger":"controller","message":"tainted node","commit":"62a726c","controller":"node.termination","controllerGroup":"","controllerKind":"Node","Node":{"name":"ip-10-0-42-250.ec2.internal"},"namespace":"","name":"ip-10-0-42-250.ec2.internal","reconcileID":"f0815e43-94fb-4546-9663-377441677028","taint.Key":"karpenter.sh/disrupted","taint.Value":"","taint.Effect":"NoSchedule"}
karpenter-55b845dd4c-tlrdr:controller {"level":"INFO","time":"2024-09-16T10:50:35.212Z","logger":"controller","message":"deleted node","commit":"62a726c","controller":"node.termination","controllerGroup":"","controllerKind":"Node","Node":{"name":"ip-10-0-42-250.ec2.internal"},"namespace":"","name":"ip-10-0-42-250.ec2.internal","reconcileID":"208e5ff7-8371-442a-9c02-919e3525001b"}
Готово.